<script>
import FormWrap from '@/components/FreeForm/render/form-wrap/index.vue'
import ExpansionWrap from '@/components/FreeForm/render/expansion-wrap/index.vue'
import { isEmpty } from 'lodash'
import { defaultValidator, defaultValueParser } from '@/components/FreeForm/utils/common'
import { getDynamicFormConfigByKey, getDynamicFormData } from '@/api/form'
import { isNumber } from 'lodash/lang'
import { parserLogic, parserOperator } from '@/components/FreeForm/libs/parser-depend'

export default {
  name: 'FormRender',
  components: { ExpansionWrap, FormWrap },
  model: {
    prop: 'data',
    event: 'input'
  },
  props: {
    data: {
      type: Object,
      default: () => {
        return {}
      }
    },
    vars: {
      type: Object,
      default: () => {
        return {}
      }
    },
    config: {
      type: Object,
      default: () => {
        return {}
      }
    },
    formCode: {
      type: String,
      default: ''
    },
    businessKey: {
      type: String,
      default: null
    },
    businessType: {
      type: String,
      default: null
    },
    readonly: {
      type: Boolean,
      default: false
    },
    isDesign: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      ctx: {
        data: {},
        vars: {},
        rules: {},
        instance: this,
        config: {}
      },
      GroupType: {
        FORM: 'form',
        EXPANSION: 'expansion'
      }
    }
  },
  watch: {
    'ctx.data': {
      handler(val) {
        this.$emit('input', val)
      },
      deep: true
    },
    'ctx.vars': {
      handler(val) {
        this.$emit('update:vars', val)
      },
      deep: true
    },
    formCode: {
      handler(val) {
        this.ctx.formCode = val
        this.fetchConfig()
      },
      immediate: true
    },
    businessKey: {
      handler(val) {
        this.ctx.businessKey = val
        this.fetchData()
      },
      immediate: true
    },
    config: {
      handler(val) {
        if (!isEmpty(val)) {
          this.ctx.config = val
          this.processDefinition((element) => {
            this.registerDefinitionEvents(element)
            this.parserDefaultValue(element)
          })
        }
      },
      immediate: true
    }
  },
  methods: {
    fetchConfig() {
      if (!isEmpty(this.formCode) && !this.isDesign) {
        const key = this.formCode
        getDynamicFormConfigByKey(key).then(resp => {
          const { data } = resp
          this.ctx.config = data
          this.fetchData()
          this.$emit('update:businessType', data.businessType)
        })
      }
    },
    fetchData() {
      if (!isEmpty(this.ctx.config.businessType) &&
        !this.isDesign) {
        const params = {
          businessType: this.ctx.config.businessType,
          businessKey: this.businessKey
        }
        getDynamicFormData(params).then(resp => {
          const { data } = resp
          this.ctx.data = data
          this.processDefinition(
            (element) => {
              this.ctx.rules = {}
              this.registerDefinitionEvents(element)
              this.parserDefaultValue(element)
              this.parserElementAttributes(element)
              this.parseElementDepends(element)
              this.$emit('create', this.ctx)
            })
        })
      }
    },
    buildGroup(elements) {
      if (!elements || elements.length === 0) {
        return []
      }
      const base = 24
      let count = 0
      const group = []
      console.log('update form view.')
      for (let i = 0; i < elements.length; i++) {
        const element = elements[i]
        /**
         * 控制是否渲染组件
         */
        if (element.hidden) {
          continue
        }
        if (!this.parseElementDepends(element)) {
          continue
        }
        const span = element.span ? element.span % base : 24
        if (count === 0 || span + count > base || span === 0) {
          group.push([element])
          count = span
          if (span === 0) {
            count = 0
          }
        } else {
          group[group.length - 1].push(element)
          count += span
        }
      }
      return group
    },
    callSpan(element) {
      if (element.span) {
        return element.span
      } else {
        return 24
      }
    },
    processDefinition(cb) {
      const config = this.ctx.config
      if (config) {
        (config.formGroup || []).forEach(group => {
          (group.elements || []).forEach(element => {
            cb(element)
          })
        })
      }
    },
    registerDefinitionEvents(element) {
      const name = element.name
      const rules = this.ctx.rules[name]
      if (element.required) {
        if (rules) {
          const validator = defaultValidator(element)
          const f = (this.ctx.rules[name] || []).filter(r => {
            return r.message === validator.message &&
              validator.trigger === r.trigger &&
              validator.required === r.required
          })
          /**
           * 如果存在，则不添加
           */
          if (f.length > 0) {
            return
          }
          console.log('register rules: ' + name)
          this.ctx.rules[name].push(validator)
        } else {
          console.log('register rules: ' + name)
          this.ctx.rules[name] = [defaultValidator(element)]
        }
      }
    },
    updateDepends() {
      this.processDefinition((element) => {
        this.parseElementDepends(element)
      })
    },
    parserDefaultValue(element) {
      const name = element.name
      const value = this.ctx.data[name]
      if (isEmpty(value)) {
        this.ctx.data[name] = defaultValueParser(element)
      }
    },
    parserElementAttributes(element) {
      const attributes = element.attributes
      try {
        element.attributes = JSON.parse(attributes)
      } catch (e) {
        console.log('Attributes is not a json string:' + element.name)
        element.attributes = {}
      }
    },
    parseElementDepends(element) {
      const attributes = element.attributes
      if (isEmpty(attributes)) {
        return true
      }
      const dependencies = attributes.dependencies || []
      if (dependencies.length > 0) {
        let flag = false
        let logic = '||'
        for (let i = 0; i < dependencies.length; i++) {
          const dep = dependencies[i]
          const { name, value, operator } = dep
          if (!isEmpty(name) && !isEmpty(value)) {
            try {
              let ret = false
              if (isNumber(value)) {
                ret = parserOperator(this.ctx.data, name, value, operator) ||
                  parserOperator(this.ctx.data, name, parseInt(value), operator)
              } else {
                ret = parserOperator(this.ctx.data, name, value, operator)
              }
              flag = parserLogic(flag, ret, logic)
              logic = dep.logic || '||'
            } catch (e) {
              console.log('dependencies parser error:' + element.name)
              return false
            }
          }
        }
        return flag
      }
      return true
    },
    targetGroup(t) {
      return (this.ctx.config.groups || [])
        .filter(g => g.type === t)
    },
    validate() {
      return new Promise((resolve, reject) => {
        this.$refs.form.validate(valid => {
          if (valid) {
            const businessType = this.ctx.config.businessType
            const businessKey = this.ctx.data.businessKey
            const { vars, data } = this.ctx
            resolve({ data: data || {}, vars: vars || {}, businessKey, businessType })
          } else {
            reject(new Error('validate failed'))
          }
        })
      })
    },
    // 获取表单实例
    getFormInstance() {
      const businessType = this.ctx.data.businessType
      const businessKey = this.ctx.config.businessKey
      const { vars, data } = this.ctx
      return {
        businessType, businessKey, vars, data
      }
    }
  }
}
</script>

<template>
  <el-form
    ref="form"
    :model="ctx.data"
    :rules="ctx.rules"
    :inline="ctx.config.inline"
    :label-width="ctx.config.labelWidth || 'auto'"
    :label-position="ctx.config.labelPosition"
    :size="ctx.config.size"
    :disabled="readonly || ctx.config.disabled"
    :hide-required-asterisk="ctx.config.hideRequiredAsterisk"
    :show-message="ctx.config.showMessage"
    :inline-message="ctx.config.inlineMessage"
  >
    <el-card v-for="(group) in targetGroup(GroupType.FORM)" :key="group.id" :shadow="'never'" style="margin-bottom: 10px" :header="group.name">
      <el-row v-for="(rowGroup,index) in buildGroup(group.elements)" :key="index" :gutter="ctx.config.gutter || 24">
        <el-col v-for="element in rowGroup" :key="element.name" :span="callSpan(element)">
          <form-wrap
            :key="element.name"
            :ctx="ctx"
            :element="element"
            :size="ctx.config.size"
            :is-design="isDesign"
            :gutter="ctx.config.gutter"
            :dict-type="element.dictKey"
            @depends="() => updateDepends()"
            v-on="$listeners"
          >
            <template v-slot="slot">
              <slot :element="slot.element" :data="ctx.data" />
            </template>
          </form-wrap>
        </el-col>
      </el-row>
    </el-card>
    <el-card v-for="(group) in targetGroup(GroupType.EXPANSION)" :key="group.id" style="margin-bottom: 10px" :header="group.name">
      <expansion-wrap
        v-for="element in group.elements"
        :key="element.name"
        :ctx="ctx"
        :element="element"
        :size="ctx.config.size"
        :is-design="isDesign"
        :gutter="ctx.config.gutter"
        :dict-type="element.dictKey"
        v-on="$listeners"
      >
        <template v-slot="scope">
          <slot :element="scope.element" :data="ctx.data" />
        </template>
      </expansion-wrap>
    </el-card>
  </el-form>
</template>

<style scoped lang="scss">
::v-deep .el-form-item__label{
  font-weight: 500!important;
}

::v-deep .el-card__header {
  padding: 10px;
  font-size: 13px;
  font-weight: bold;
  border-left: 4px solid #003780;
  background: #fafafa;
}

.el-card {
  border-top-right-radius: 0;
  border-top-left-radius: 0;
}
</style>
